<?php
/**
 * @package    panel
 * @subpackage panel.application
 */
/**
 * Web Front controller.
 * Central entry point for handling requests.
 *
 *\msc
 *hscale="1.5";
 *Client,Request,Application,Controller,Environment,Authorization,Exception,Response;
 *Client:>Application [label="Client send request to application", URL="\ref panel__application::process()"];
 *--- [label="Application initialization (if init = TRUE)"];
 *Application=>>Environment [label="Init environment\n(DB init, Test file permissions, Logs)", URL="\ref panel__environment::init()"];
 *Application=>>Application [label="Start session"];
 *Application=>>Request [label="Prepare request (b64j decode ?_pd)", URL="\ref panel__application__request::init()"];
 *--- [label="Re-mapping request"];
 *Application=>>Application [label="Put default request parameters if required", URL="\ref panel__application::map()"];
 *--- [label="Authorization check (if authorize = TRUE)"];
 *Application=>>Authorization [label="Do autorization checks", URL="\ref panel__application::authorize()"];
 *...;
 *Authorization->Request [label="Is failed?\n(not logged in, not active, wrong something)\n-> Bind (internal) request\nto redirect on login page", URL="\ref panel__application::authorize()"];
 *--- [label="Process request-response processor"];
 *Application<<=Request [label="Provides data about requested controller, action, data"];
 *Application=>>Controller [label="Find controller, make instance"];
 *Application=>>Controller [label="Process"];
 *Controller=>>Application [label="Return response data"];
 *Application=>>Response   [label="Put data into response object, process()"];
 *Application<<=Response   [label="Return processed response, send headers"];
 *--- [label="Response"];
 *
 *Client<:Application [label="Application send response to client", URL="\ref panel__application__response::process()"];
 *...;
 *--- [label="Shutdown"];
 *Client<:Exception [label="On case of exception, will be generated error response", URL="\ref panel__application::catchException()"];
 *Application=>>Application [label="Close session"];
 *\endmsc
 *
 * @author  Dmitry Bakaleinik (code)
 */
abstract class panel__application
	implements maf__mvc__i__controller__static {

	/**
	 * Process client request
	 *
	 *<pre>
	 * Options array(key => value):
	 * init          - Do initialization of environment, session, log systems etc. (true)
	 * authorize     - Check authorization (true)
	 * session_close - Close session on complete? (true)
	 *</pre>
	 *
	 * @param array $_ options
	 */
	static public function process( array $_ = array() ) {

		$time = microtime(true);

		if( isset_or($_["init"], TRUE) ) {

			/**
			 * Init environment
			 */
			panel__environment::getInstance()->init();

			/**
			 * Log
			 */
			panel__logger__access::getInstance()->log();

			/**
			 * Session
			 */
			if( !session_id() ) {
				session_start(); }

			/**
			 * Prepare request
			 */
			panel__application__request::init();

		}	# if

		self::map();

		$response = self::processRRP($_);

		if( isset_or($_["session_close"], TRUE) ) {

			if( session_id() ) {
				session_commit(); }

			if( panel__settings__environment::isVerbose("panel.application.process.statistics") ) {

				p()->console( sprintf(
					"Query executions: %s times.",
					p()->db()->getSQLExecutionCount()
				) );

				p()->console( sprintf(
					"Memory usage: %s kbytes, peak %s kbytes.",

					function_exists("memory_get_usage") ?
						(memory_get_usage(TRUE) / 1024) : "",

					function_exists("memory_get_peak_usage") ?
						(memory_get_peak_usage(TRUE) / 1024) : ""
				) );

				p()->console("Time:", microtime(true) - $time);

			}	# if

		}	# if

		return $response;

	}	# Function

	/**
	 * Map panel.request _p, _pa, _rt to default values
	 *
	 * In cases:
	 * 1. No _p, _pa passed
	 * 2. If they are broken.
	 *
	 * @return bool
	 */
	static protected function map() {

		/* IF EMPTY PROCESSOR => INDEX */
		if( panel__application__request::getProcessorName() == "" ) {

			panel__application__request::setProcessorName("ui");
			panel__application__request::setProcessorAction("index");

			panel__application__request
			::setResponseType( panel__application__response::TYPE__PANEL__LAYOUT );

		}	# if

		/* FIX EMPTY RESPONSE TYPE TO LAYOUT */
		if( !panel__application__request::getResponseType() ) {

			panel__application__request
			::setResponseType( panel__application__response::TYPE__PANEL__LAYOUT );

		}	# if

		return TRUE;

	}	# Function

	/**
	 * Call an RRP (Request-Response-Processor) controller for request processing accoring panel.request data.
	 *
	 *<pre>
	 * Flow:
	 * 1. Detect proper RRP class. If not found (obsolete call?) find controller_processor class.
	 * 2. Make an instance of RRP class.
	 * 3. Pass response object, request action and data to RRP instance. Perform processing.
	 * 4. Process response object (it should have response data given from processing at P3) to make output.
	 *</pre>
	 *
	 * @param array $_ options (given from process({options}))
	 * @return mixed
	 */
	static protected function processRRP( array $_ = array() ) {

		// Prepare

		$processor_class = panel__application__request::getProcessorName();
		$response_type   = panel__application__request::getResponseType();

		// Validate

		$processor_class = p()->validate($processor_class, "string!", "Processor");
		$response_type   = p()->validate($response_type,   "string!", "Response type");

		$processor_class = sprintf("panel__rrp__%s", $processor_class);

		// Check rrp existence
		if( !class_exists($processor_class, TRUE) ) {
			throw new panel__e__unknown_processor($processor_class); }

		// Check rrp instancebility
		$reflection = new ReflectionClass($processor_class);

		if( !$reflection->isInstantiable() ) {

			$e = new panel__e__unknown_processor($processor_class);
			$e[] = "Is not instantiable";
			throw $e;

		}	# if

		$response = new panel__application__response;
		$response->setType($response_type);

		$processor = new $processor_class;

		$response->setResponse( $processor->process(
			panel__application__request::getProcessorAction(),
			panel__application__request::getProcessorData(),
			$response
		) );

		return $response->process( isset_or($_["response_silent"]) );

	}	# Function

	/**
	 * Dump data into debug console/log file.
	 *
	 * @param mixed $_,... What to dump UNLIMITED
	 */
	static public function console() {

		static $c;

		isset_or_set($c, 0);

		$t = debug_backtrace();
		$m = sprintf(
			"=== %s. Call: %s%s%s() [Line: %s] \n",
			++$c,
			isset_or($t[1]["class"], ""),
			isset_or($t[1]["type"], ""),
			isset_or($t[1]["function"], "main"),
			isset_or($t[0]["line"], "??")
		);

		$a = func_get_args();

		panel__logger__console::getInstance()->log($m);

		foreach( $a as $_a ) {
			panel__logger__console::getInstance()->log($_a); }

		if( panel__settings__environment::isOnCLI() ) {

			echo $m, "\n";

			foreach( $a as $_a ) {
				echo $_a, "\n"; }

		} else if( !headers_sent() ) {

			FirePHP::getInstance(TRUE)->fb($m);

			foreach( $a as $_a ) {
				FirePHP::getInstance(TRUE)->fb($_a); }

		}	# if

	}	# Function

	/**
	 * Dump trace to FirePHP debugger
	 * @param $_label_ Label of trace
	 */
	static public function consoleTrace($_label_) {

		FirePHP::getInstance(TRUE)->trace($_label_);

	}	# Function

	/**
	 * Application main exception catcher.
	 * All exceptions became to heaven and log reporting,
	 * except exceptions instance of panel__e__i_displayable_to_user
	 *
	 * @param exception $_e_
	 */
	static public function catchException( exception $_e_ ) {

		try {

			if( !($_e_ instanceof panel__e__i_displayable_to_user) ) {

				panel__logger__errors::getInstance()->logException($_e_);
				panel__logger__errors::getInstance()->logDump();

			}	# if

			if( $_e_ instanceof panel__e__exception ) {

				$response = new panel__application__response;
				$response->setType( panel__application__response::TYPE__TEXT__HTML );

				if( panel__settings__environment::isVerbose() ) {

					$response->setResponse( $_e_->__toString() );

				} else {

					switch( panel__application__request::getResponseType() ) {

					case panel__application__response::TYPE__PANEL__LAYOUT:
					case panel__application__response::TYPE__TEXT__HTML:

						$ui = new panel__rrp__ui;

						$response->setResponse( $ui->process(
							"error",
							$_e_->getDisplayID() . ": " . $_e_->getMessage(),
							$response
						) );

						$response->setType( panel__application__response::TYPE__PANEL__LAYOUT );

						break;

					case panel__application__response::TYPE__TEXT__HTML__EJSON:
					case panel__application__response::TYPE__TEXT__JSON:

						$response->setResponse( array("error" => array(
							"message" => $_e_->getMessage(),
							"code"    => $_e_->getDisplayID(),
						) ) );

						$response->setType( panel__application__response::TYPE__TEXT__JSON );

						break;

					case panel__application__response::TYPE__TEXT__XML:

						$xml = new XMLWriter();

						$xml->openMemory();
						$xml->startDocument("1.0", "UTF-8");
						$xml->startElement("error");
						$xml->writeElement("message", $_e_->getMessage());
						$xml->endElement();

						$response->setType( panel__application__response::TYPE__TEXT__XML );

						$response->setResponse( $xml );

						break;

					default:

						$response->setResponse(
							panel__settings__environment::isOnCLI() ?
								$_e_->getMessage() :
								htmlentities($_e_->getMessage())
						);

						break;

					}	# switch

				}	# if

				if( is_numeric($_e_->getDisplayID()) ) {
					$response->addHeader(NULL, $_e_->getDisplayID()); }

				$response->process();

			} else {

				if( !headers_sent() ) {
					header("HTTP/1.1 500 Internal Server Error", TRUE, 500); }

				if( panel__settings__environment::isDebug() ) {

					maf__e2e::catchException($_e_);

					self::console($_e_);

				} else {

					echo nl2br( sprintf(
						"%sPANEL INTERNAL ERROR\nContact system administrator.\n%s\n%s",
						ini_get("error_prepend_string"),
						$_e_->getMessage(),
						ini_get("error_append_string")
					) );

				}	# if

			}	# if

		} catch ( exception $e ) {

			if( panel__settings__environment::isDebug() ) {

				maf__e2e::catchException($_e_);

			} else {

				echo "PANEL FATAL INTERNAL ERROR:\n", $e->__toString();

			}	# if

		}	# try

		exit;

	}	# Function

}	# Class

/* EOF */